// EnerMon - Energy Monitor with Arduino/Jeenode

#include <Ports.h>
#include <RF12.h> 

enum { MEASURE, REPORT, TASK_LIMIT };

#define REPORT_PERIOD    200    // sec/10
#define MEASURE_PERIOD   150

#define SERIAL  1

static word schedBuf[TASK_LIMIT];
Scheduler scheduler (schedBuf, TASK_LIMIT);

// this has to be added since we're using the watchdog for low-power waiting
ISR(WDT_vect) { Sleepy::watchdogEvent(); }

//Setup variables
int numberOfSamples = 3000;

// This defines the structure of the packets which get sent out by wireless:
struct {
    double realPower;
    double apparentPower;
    double powerFactor;
    double Vrms;
    double Irms;
    byte lobat :1;  // supply voltage dropped under 3.1V: 0..1
} payload;

//Set Voltage and current input ANALOG pins
int inPinV = 3;
int inPinI = 2;

// Voltage is reduced both by wall xfmr & voltage divider
#define AC_WALL_VOLTAGE        230
#define AC_ADAPTER_VOLTAGE     7
// Ratio of the voltage divider in the circuit
#define AC_VOLTAGE_DIV_RATIO   9.5
// CT: Voltage depends on current, burden resistor, and turns
#define CT_BURDEN_RESISTOR    105
#define CT_TURNS              1500

//Calibration coeficients
//These need to be set in order to obtain accurate results
//Set the above values first and then calibrate futher using normal calibration method described on how to build it page.
double VCAL = 1;
double ICAL = 1;
double PHASECAL = 2.3;

double VCC = 3.3;

// Initial gueses for ratios, modified by VCAL/ICAL tweaks
#define AC_ADAPTER_RATIO       (AC_WALL_VOLTAGE / AC_ADAPTER_VOLTAGE)
double V_RATIO = AC_ADAPTER_RATIO * AC_VOLTAGE_DIV_RATIO * VCC / 1024 * VCAL;
double I_RATIO = (long double) CT_TURNS / CT_BURDEN_RESISTOR * VCC / 1024 * ICAL;

//Sample variables
int lastSampleV,lastSampleI,sampleV,sampleI;

//Filter variables
double lastFilteredV, lastFilteredI, filteredV, filteredI;
double filterTemp;

//Stores the phase calibrated instantaneous voltage.
double shiftedV;

//Power calculation variables
double sqI,sqV,instP,sumI,sumV,sumP;

//Useful value variables
double realPower,
       apparentPower,
       powerFactor,
       Vrms,
       Irms;

static void doMeasure() {
    //Reset accumulators
    sumV = 0;
    sumI = 0;
    sumP = 0;
    
    for (int n=0; n<numberOfSamples; n++) {
      //Used for offset removal
      lastSampleV=sampleV;
      lastSampleI=sampleI;
      
      //Read in voltage and current samples.
      sampleV = analogRead(inPinV);
      sampleI = analogRead(inPinI);
      
      //Used for offset removal
      lastFilteredV = filteredV;
      lastFilteredI = filteredI;
      
      //Digital high pass filters to remove 2.5V DC offset.
      filteredV = 0.996*(lastFilteredV+sampleV-lastSampleV);
      filteredI = 0.996*(lastFilteredI+sampleI-lastSampleI);
      
      //Phase calibration goes here.
      shiftedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);
      
      //Root-mean-square method voltage
      //1) square voltage values
      sqV= filteredV * filteredV;
      //2) sum
      sumV += sqV;
      
      //Root-mean-square method current
      //1) square current values
      sqI = filteredI * filteredI;
      //2) sum 
      sumI += sqI;
      
      //Instantaneous Power
      instP = shiftedV * filteredI;
      //Sum
      sumP +=instP;
    }

  //Calculation of the root of the mean of the voltage and current squared (rms)
  //Calibration coeficients applied. 
  Vrms = V_RATIO*sqrt(sumV / numberOfSamples); 
  Irms = I_RATIO*sqrt(sumI / numberOfSamples); 

  //Calculation power values
  realPower = V_RATIO*I_RATIO*sumP / numberOfSamples;
  apparentPower = Vrms * Irms;
  powerFactor = abs(realPower / apparentPower);

}

static void doReport() {
    payload.realPower = realPower;
    payload.apparentPower = apparentPower;
    payload.powerFactor = powerFactor;
    payload.Vrms = Vrms;
    payload.Irms = Irms;
    payload.lobat = rf12_lowbat();

    rf12_sleep(-1);
    while (!rf12_canSend())
        rf12_recvDone();

    rf12_sendStart(0, &payload, sizeof payload, 2);
    rf12_sleep(0);
      
#if SERIAL  
    //Output to serial
    Serial.print(realPower);
    Serial.print(' ');
    Serial.print(apparentPower);
    Serial.print(' ');
    Serial.print(powerFactor);
    Serial.print(' ');
    Serial.print(Vrms);
    Serial.print(' ');
    Serial.print(Irms);
    Serial.print(' ');
    Serial.println(payload.lobat);
    delay(2);
#endif
     
}

void setup () {
#if SERIAL
    Serial.begin(57600);
    Serial.println("\n[EnerMon]");
#endif

    rf12_config();
    rf12_sleep(0); // power down
    
    // start both tasks 1.5 seconds from now
    scheduler.timer(MEASURE, 15);
    scheduler.timer(REPORT, 15);
}

void loop () {
    switch (scheduler.pollWaiting()) {
  
        case MEASURE:
            scheduler.timer(MEASURE,MEASURE_PERIOD);
            doMeasure();
            break;
        
        case REPORT:
            scheduler.timer(REPORT,REPORT_PERIOD);
            doReport();
            break;
    }
}

